/***************************************************************************
 *   oboe.controllers.DebugConsole                                         *
 *   Copyright (C) 2011 by Felipe Manga                                    *
 *   lufeboma@gmail.com                                                    *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

package oboe.controllers.debugConsole
{
	import com.adobe.serialization.json.JSON;
	import com.adobe.utils.StringUtil;
	
	import flash.display.DisplayObject;
	import flash.net.FileReference;
	import flash.text.TextField;
	import flash.utils.Dictionary;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	
	import oboe.controllers.jml.JMLController;
	import oboe.controllers.jml.helpers.JMLButton;
	import oboe.controllers.jml.helpers.JMLNode;
	import oboe.controllers.jml.helpers.list.TextList;
	import oboe.controllers.jml.helpers.window.JMLWindow;
	import oboe.controllers.scripting.Interpretor;
	import oboe.core.Oboe;
	
	public class DebugConsole extends JMLController
	{
		private var styleJML:Object =
			{	
				
			};
		
		private var structureJML:Array =
			[
				{"@pipe":"onToggleDebugConsole"},
				{"@pipe":"onCloseWindow"},
				{"@pipe":"onClickSnapshotJSON"},
				{"@pipe":"onClickSnapshotMM"},
				{"@pipe":"onRunCommand"},
				
				{
					"Sprite:btnDebugToggle" : 
					{
						"y":20,
						
						"@call":
						[
							{"graphics.beginFill":[16777215, 0.5]}, // 11579568, 0.1]},
							{"graphics.drawCircle":[5, 10, 25]},
							{"graphics.endFill":[]}
						],

						"@callback": ["click", "signalUp", "onToggleDebugConsole"],
						
						"mouseChildren" : false,
						"mouseEnabled" : true,
						
						"Label:lblDebugToggle" :
						{
							"text":"ODC"
						}
						
					}
				},
				
				{
					"Window:DebugConsole":
					{
						"@pipe:1":"onCloseWindow",
						"@pipe:2":"onClickSnapshotMM",
						"@pipe:3":"onClickSnapshotJSON",
						"@pipe:4":"onRunCommand",
						
						"title":"Oboe Debug Console",
						"width":400,
						"height":310,
						"visible":false,
						"showResizeButton":false,
						
						"Button:SnapshotJSON":
						{
							"x":5,
							"y":25,
							"width":100,
							"height":25,
							"text":"JSON Snapshot"
						},
						
						"Button:SnapshotMM":
						{
							"x":110,
							"y":25,
							"width":100,
							"height":25,
							"text":"MM Snapshot"
						},
						
						"JMLInput:CMD":
						{
							"x":5,
							"y":53,
							"width":390,
							"height":20,
							"background":true,
							"backgroundColor":0,
							"border":true,
							"borderColor":0x00FF00,
							"textColor":0x00FF00,
							"@callback": ["enter", "signalUp", "onRunCommand"]
						},
						
						"TextList:Content":
						{
							"x":5,
							"y":75,
							"width":390,
							"height":310-80,
							"showVBar":true,
							"showHBar":true
						}
					}
				}
			];
		
		
		public function DebugConsole()
		{
			super( styleJML );
			this.addControllerType("Window", JMLWindow);
			this.addDisplayObjectType("Button", JMLButton);
			this.addDisplayObjectType("TextList", TextList);
			this.loadStructure( structureJML );
			
			this.content = this.getNodeFromId("Content").getAsset() as TextList;
			this.addChild( new Interpretor("broadcastSignal") );
		}
		
		[slot] public function onRunCommand():void
		{
			var tf:TextField = this.getNodeFromId("CMD").getAsset() as TextField;
			this.signalDown("runCommands", [tf.text]);
			tf.text = "";
		}
		
		private var acc:Number = 0;
		private var content:TextList;
		private var varreg:Object;
		[slot] public function onTick( time:Number ):void
		{
			var disp:DisplayObject = this.getDisplayObject();
			disp.parent.setChildIndex( disp, disp.parent.numChildren-1 );
			acc += time;
			if( acc >= 1 )
			{
				if( !this.varreg )
				{
					this.varreg = {};
				}
				for( var val:String in Oboe.flags )
				{
					if( val in varreg )
					{
						content.setItem( varreg[val], val + " : " + Oboe.flags[val] );
					}
					else
					{
						varreg[val] = content.pushItem( val + " : " + Oboe.flags[ val ] );
					}
				}
			}
		}
		
		[slot(alias=onCloseWindow)] public function onToggleDebugConsole():void
		{
			var w:JMLNode = this.getNodeFromId("DebugConsole");
			var b:JMLNode = this.getNodeFromId("btnDebugToggle");
			var t:Boolean = !w.getProperty("visible");
			w.setProperty("visible", t);
			b.setProperty("visible", !t);
		}
		
		private var ss:FileReference;
		private var hit:Dictionary;
		private var clouds:Array = 
			[
				"E0E0E0",
				"D0D0D0",
				"C0C0C0",
				"B0B0B0",
				"A0A0A0",
				"B0B0B0",
				"C0C0C0",
				"D0D0D0",
				"E0E0E0"
			];
		[slot] public function onClickSnapshotJSON():void
		{
			ss = new FileReference();
			hit = new Dictionary();
			hit[ this ] = true;
			var data:Object = JSON.encode( snapshot( Oboe.root ) );
			ss.save( data, "snapshot.json" );
			hit = null;
		}

		[slot] public function onClickSnapshotMM():void
		{
			ss = new FileReference();
			hit = new Dictionary();
			hit[ this ] = true;
			var data:String = "<map version=\"0.9.0\"><node TEXT=\"StateMachine\">" + this.genMM( snapshot( Oboe.root ), true, 0 ) + "</node></map>";
			ss.save( data, "snapshot.mm" );
			hit = null;
		}
		
		private function genMM( obj:Object, pos:Boolean, depth:int, icon:String = "" ):String
		{
			var acc:String = "";
			var tag:String;
			var closeTagS:String;
			var closeTagM:String;
			if( pos )
			{
				tag = "<node POSITION=\"right\" ";
			}
			else
			{
				tag = "<node ";
			}
			
			if( icon != "" )
			{
				closeTagS = "><icon BUILTIN=\"" + icon + "\"/></node>";
				closeTagM = "<icon BUILTIN=\"" + icon + "\"/></node>";
			}
			else
			{
				closeTagS = "/>";
				closeTagM = "</node>";
			}
			
			if( obj is String || obj is int || obj is Boolean || obj is Number || obj == null )
			{
				acc += tag + "TEXT=\"" + obj + "\" " + closeTagS;
			}
			else if( obj is Array )
			{
				for each( var aval:Object in obj )
				{
					if( aval is String || aval is int || aval is Boolean || aval is Number || aval == null )
					{
						acc += genMM( aval, false, depth, icon );
					}
					else
					{
						acc += tag + "TEXT=\"" + aval.constructor.toString() + "\">" + genMM( aval, false, depth, icon ) + closeTagM;
					}
				}
			}
			else if( "isSigSlotInfo" in obj )
			{
				var match:Function = function( key:String, icon:String, obj:Object ):void
				{
					acc += genMM( obj[key], false, depth, icon );
				};
				
				match( "children", "", obj );
				match( "slots", "bell", obj );
				match( "pipes", "up", obj );
				match( "meta",  "idea", obj );
			}
			else
			{
				for( var key:String in obj )
				{
					var val:Object = obj[key];
					var altkey:String = key;
					if( "__instanceCount" in val )
					{
						altkey = key + " (x" + val.__instanceCount + ")";
					}
					acc += tag;
					acc += "FOLDED=\"true\" ";
					acc += "TEXT=\"" + altkey + "\">" + genMM( val, false, depth+1, icon );
					acc += "<cloud COLOR=\"#" + clouds[ depth%clouds.length ] + "\"/>";
					acc += closeTagM;
				}
			}
			return acc;
		}
		
		private function getMetaRec( fc:Class, ret:Array, index:Object ):void
		{
			var dt:XMLList = describeType(fc)..constant;
			var len:int = dt.length();
			for( var i:int=0; i<len; ++i )
			{
				var name:String = dt.@name[i];
				if( StringUtil.beginsWith(name, "SIG_") && !(fc[name] in index) )
				{
					ret.push( fc[ name ] );
				}
			}
		}
		
		private function getMeta( o:Object, index:Object ):Array
		{
			var ret:Array = [];
			var fc:Class = o.constructor;
			var dt:XMLList = describeType(fc).factory.extendsClass;
			var len:int = dt.length();
			getMetaRec( fc, ret, index );
			for( var i:int=0; i<len; ++i )
			{
				var name:String = dt.@type[i].toString().replace(/::/g, ".");
				var t:Class = getDefinitionByName( name ) as Class;
				getMetaRec( t, ret, index );
			}
			
			dt = describeType( o )..variable;
			len = dt.length();
			for( i=0; i<len; ++i )
			{
				name = dt.@name[i];
				if( StringUtil.beginsWith(name, "SIG_") && !(o[name] in index) )
				{
					ret.push( o[ name ] );
				}
			}
			return ret;
		}
		
		private function snapshot( c:Object ):Object
		{
			var ret:Object = { isSigSlotInfo:true };
			hit[ c ] = true;
			var slots:Object = {};
			var pipes:Object = {};
			var children:Object = {};
			var meta:Object = {};
			
			c.message("_getSigSlotInfo", children, pipes, slots, meta );
			
			ret.meta = getMeta( c, meta );

			var childClasses:Dictionary = new Dictionary(true);

			ret.children = {};
			for each( var entries:Array in children )
			{
				for each( var child:Object in entries )
				{
					if( child == this )
					{
						continue;
					}
					if( child.constructor in childClasses )
					{
						if( !("__instanceCount" in ret.children[ child ] ) )
						{
							ret.children[ child ].__instanceCount = 1;
						}
						ret.children[ child ].__instanceCount++;
					}
					else
					{
						ret.children[child] = snapshot( child );
						childClasses[ child.constructor ] = true;
					}
				}
			}
			
			ret.pipes = [];
			for each( var pipe:String in pipes )
			{
				ret.pipes.push( pipe ); 
			}

			for each( var sig:String in meta )
			{
				ret.meta.push( sig ); 
			}
			
			ret.slots = [];
			for each( var slot:String in slots )
			{
				ret.slots.push( slot ); 
			}
			
			return ret;
		}
	}
}
